/*
 * Copyright (c) 2023 Huawei Device Co., Ltd. All rights reserved.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "gwpasan_collector.h"

#include <cstdint>
#include <cerrno>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <fcntl.h>
#include <mutex>
#include <securec.h>
#include <sys/time.h>
#include <unistd.h>
#include <vector>

#include "bundle_mgr_client.h"
#include "faultlog_util.h"
#include "file_util.h"
#include "logger.h"
#include "hisysevent.h"

DEFINE_LOG_LABEL(0xD002D12, "Sanitizer");

static std::stringstream g_asanlog;

void WriteGwpAsanLog(char* buf, size_t sz)
{
    if (buf == nullptr || sz == 0) {
        return;
    }

    static std::mutex sMutex;
    std::lock_guard<std::mutex> lock(sMutex);
    // append to buffer
    for (size_t i = 0; i < sz; i++) {
        g_asanlog << buf[i];
    }

    char *output = strstr(buf, "End GWP-ASan report");
    if (output) {
        std::string asanlog = g_asanlog.str();
        // parse log
        ReadGwpAsanRecord(asanlog);
        // clear buffer
        g_asanlog.str("");
    }
}

void ReadGwpAsanRecord(std::string& gwpAsanBuffer)
{
    GwpAsanCurrInfo currInfo;
    char bundleName[BUF_SIZE];
    currInfo.description = gwpAsanBuffer;
    currInfo.pid = getpid();
    currInfo.uid = getuid();
    currInfo.errType = "GWP-ASAN";
    if (GetNameByPid(static_cast<pid_t>(currInfo.pid), bundleName) == true) {
        currInfo.procName = std::string(bundleName);
    }
    if (currInfo.uid >= MIN_APP_UID) {
        currInfo.appVersion = GetApplicationVersion(currInfo.uid, currInfo.procName);
    } else {
        currInfo.appVersion = "";
    }
    time_t timeNow = time(nullptr);
    uint64_t timeTmp = timeNow;
    std::string timeStr = OHOS::HiviewDFX::GetFormatedTime(timeTmp);
    currInfo.happenTime = std::stoll(timeStr);

    // Do upload when data ready
    WriteCollectedData(currInfo);
    HiSysEventWrite(OHOS::HiviewDFX::HiSysEvent::Domain::RELIABILITY, "ADDR_SANITIZER",
                    OHOS::HiviewDFX::HiSysEvent::EventType::FAULT,
                    "MODULE", currInfo.procName,
                    "VERSION", currInfo.appVersion,
                    "REASON", currInfo.errType,
                    "PID", currInfo.pid,
                    "UID", currInfo.uid,
                    "SUMMARY", currInfo.description,
                    "HAPPEN_TIME", currInfo.happenTime);
}

bool WriteNewFile(const int32_t fd, const GwpAsanCurrInfo &currInfo)
{
    if (fd < 0) {
        return false;
    }

    OHOS::HiviewDFX::FileUtil::SaveStringToFd(fd, std::string("Generated by HiviewDFX @OpenHarmony") + "\n" +
        std::string("=================================================================\n") +
        "TIMESTAMP:" + std::to_string(currInfo.happenTime) +  "\n" +
        "Pid:" + std::to_string(currInfo.pid) + "\n" +
        "Uid:" + std::to_string(currInfo.uid) + "\n" +
        "Process name:" + currInfo.procName + "\n" +
        "Fault thread info:\n" + currInfo.description);

    close(fd);
    return true;
}

std::string CalcCollectedLogName(const GwpAsanCurrInfo &currInfo)
{
    std::string filePath = "data/log/faultlog/faultlogger/";
    std::string prefix = "gwpasan";
    std::string name = currInfo.procName;
    if (name.find("/") != std::string::npos) {
        name = currInfo.procName.substr(currInfo.procName.find_last_of("/") + 1);
    }

    std::string fileName = "";
    fileName.append(prefix);
    fileName.append("-");
    fileName.append(name);
    fileName.append("-");
    fileName.append(std::to_string(currInfo.pid));
    fileName.append("-");
    fileName.append(std::to_string(currInfo.happenTime));

    std::string fullName = filePath + fileName;
    return fullName;
}

void WriteCollectedData(const GwpAsanCurrInfo &currInfo)
{
    std::string fullName = CalcCollectedLogName(currInfo);
    if (fullName.size() == 0) {
        return;
    }
    int32_t fd = CreateLogFile(fullName);
    if (fd < 0) {
        return;
    }
    WriteNewFile(fd, currInfo);
}

int32_t CreateLogFile(const std::string& name)
{
    int32_t fd = -1;

    fd = open(name.c_str(), O_CREAT | O_WRONLY | O_TRUNC);
    if (fd < 0) {
        HIVIEW_LOGE("Fail to create %{public}s,  err: %{public}s.",
            name.c_str(), strerror(errno));
    }
    return fd;
}

bool GetNameByPid(pid_t pid, const char procName[])
{
    char pidPath[BUF_SIZE];
    char buf[BUF_SIZE];
    bool ret = false;

    sprintf_s(pidPath, BUF_SIZE, "/proc/%d/status", pid);
    FILE *fp = fopen(pidPath, "r");
    if (fp != nullptr) {
        if (fgets(buf, BUF_SIZE - 1, fp) != nullptr) {
            if (sscanf_s(buf, "%*s %s", procName, MAX_PROCESS_PATH) == 1) {
                ret = true;
            }
        } else {
            ret = false;
        }
        fclose(fp);
    }

    return ret;
}

std::string GetApplicationVersion(int32_t uid, const std::string& bundleName)
{
    OHOS::AppExecFwk::BundleInfo info;
    OHOS::AppExecFwk::BundleMgrClient client;
    if (!client.GetBundleInfo(bundleName, OHOS::AppExecFwk::BundleFlag::GET_BUNDLE_DEFAULT,
        info, OHOS::AppExecFwk::Constants::ALL_USERID)) {
        HIVIEW_LOGW("Failed to query BundleInfo from bms, uid:%{public}d.", uid);
        return "";
    } else {
        HIVIEW_LOGI("The version of %{public}s is %{public}s", bundleName.c_str(),
            info.versionName.c_str());
    }
    return info.versionName;
}
